# player
"""
Defines the Player object which abstracts the top-level Lib-Ray software player.
"""

# Python standard library
from __future__ import print_function
import os, sys, urllib
from Queue import Queue
import BaseHTTPServer, SimpleHTTPServer, SocketServer, urlparse
import thread
import ctypes

# 3rd-Party Dependencies (GTK, WebKit, VLC)
import gobject
import gtk
gtk.gdk.threads_init()

import webkit
import vlc

# Package internals
import config
import model
import utils

class Player(object):
    """
    Top-level abstraction of a Lib-Ray software player. Runs everything.
    Initial implementation must be initialized with an already-mount volume path.
    Future versions may implement some kind of device/volume discovery process.
    """
    def __init__(self, volume_path=None):
        if not volume_path:
            raise NotImplemented("Must launch with a path to an already-mounted Lib-Ray volume.")
          
        # Define major components
        self.browser = Browser()
        self.cinema  = Cinema()
        self.controller = Controller(self.browser, self.cinema, self)
        
        # Load a volume and launch
        self.volume = model.Volume(volume_path)
        self.browser.load(self.volume.index)
        self.browser.focus()            
            
    def act(self, event):
        """
        Act on a controller event - handler.
        """
        if event == controller.QUIT_EVENT:
            gtk.main_quit()

            
class Controller(object):
      """
      Listener object that collates signals from several input sources to control Lib-Ray Player.
      """
      event_types = (gtk.gdk.BUTTON_PRESS, gtk.gdk.KEY_PRESS, gtk.gdk.KEY_RELEASE)
      actions = Queue()
      
      # gtk.gdk._2BUTTON_PRESS, gtk.gdk._3BUTTON_PRESS, gtk.gdk.BUTTON_RELEASE, 
      # gtk.gdk.PROXIMITY_IN, gtk.gdk.PROXIMITY_OUT, 
      # gtk.gdk.FOCUS_CHANGE
      def __init__(self):
          pass

      def connect(self, browser, cinema):
          # References to use for signaling
          self.browser = browser
          self.cinema  = cinema
          #self._player  = player          
          self.key_dispatch = {
              'a':((print, ('Key pressed -> a',)),),
              'b':((print, ('Key pressed -> b',)),),
              'Escape': ((gtk.main_quit, ()),),
              'u':((cinema.unfullscreen, ()),),
              'q':((browser.focus, ()), (cinema.stop, ()),),
              'p':((cinema.focus, ()), (cinema.play, ()),),
              }
              
          self.http_dispatch = {
              'play':((cinema.focus, ()), (cinema.play, ()),),
              }
          gtk.gdk.event_handler_set(self._gtk_signal_handler)
          self.idle_id = gtk.idle_add(self.act)
          
      def _gtk_signal_handler(self, event, data=None):
          """
          Handler to attach to gdk/gtk signal system. Filters and catches Lib-Ray related events,
          and feeds them to the Controller queue.
          """
          if event.type==gtk.gdk.KEY_PRESS:
              name = gtk.gdk.keyval_name(event.keyval)
              print("Key name = '%s'." % name)
              if name in self.key_dispatch.keys():
                  print("Dispatching ('%s') action" % name)
                  for func, args in self.key_dispatch[name]:
                      self.actions.put((func, args))
              elif name=='q':
                  gtk.main_quit()
              elif name=='l':
                  print("%d Actions Waiting." % self.actions.qsize())
              else:
                  gtk.main_do_event(event)
          else:
              gtk.main_do_event(event)

       
      def _server_dispatch(self, name, value):
          """
          Handler to catch server dispatch calls and store them in the control queue.
          """
          if name in self.http_dispatch.keys():
              for func, filt in self.http_dispatch[name]:
                  if filt:
                    self.actions.put((func, filt(value)))
                  else:
                    self.actions.put((func, ()))
            
      def _autoquit(self, event, ptr):
          """
          Called at end of video playback to return to menu.
          """
          print("AUTOQUIT CALLED")
          self.actions.put((self.browser.focus, ()))
          self.actions.put((self.cinema.stop, ()))
                                    
      def act(self, data=None):
          """
          Process commands to empty the command queue.
          """
          while not self.actions.empty():
              func, args = self.actions.get()
              if args is not None:
                  func(*args)
              else:
                  func()
          return True

            
class Server(BaseHTTPServer.HTTPServer):
    ROOT  = config.PLAYER_MENU_ROOT
    PORTS = (8765, 8766, 8767, 8768, 8769, 8770)
    
    def __init__(self):
        for port in self.PORTS:
            try:
              BaseHTTPServer.HTTPServer.__init__(self,
                                     ('', port), LibRayHTTPHandler)
              self.port = port
              break
            except:
              print(sys.exc_info())
              break

    def start(self, controller):
        #logger.info("Lib-Ray server operating on port %d" % PORT)
        thread.start_new_thread(self.serve_forever, ())
        return self.port
        
    def stop(self):
        self.shutdown()
        
        
class LibRayHTTPHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):

    def do_GET(self):
        """
        Serve GET requet, intercepting and dispatching GET parameters.
        """
        # Parse query data & params to find out what was passed
        parsedParams = urlparse.urlparse(self.path)
        queryParsed = urlparse.parse_qs(parsedParams.query)
        if len(queryParsed) > 0:
            self._dispatch(queryParsed)  
        SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
        
    def translate_path(self, path):
        """
        Interpret path part of URI and map to file system.
        
        The top level directory is handled specially:
        
        http://localhost:PORT/player/ -> Goes to the player menus
        http://localhost:PORT/lib/ -> Goes to player-provided libraries (Javascript)
        http://localhost:PORT/VOL_ID/ -> Goes to a Lib-Ray volume
        """
        path = path.split('?',1)[0]
        path = path.split('&',1)[0]
        path = os.path.normpath(urllib.unquote(path))
        words = path.split('/')
        words = [w for w in words if w]
        if words[0]=='player':
            # Player menu URL
            path = config.PLAYER_MENU_ROOT
        elif words[0]=='lib':
            # Library URL
            path = config.LIB_PATH
        else:
            path = os.path.join(config.VOLUMES_PATH, words[0])
        for word in words[1:]:
            path = os.path.join(path, word)
        path = os.path.normpath(path)
        return path
        
       
    def _dispatch(self, calls):
        """
        Examine passed parameters and dispatch calls accordingly.
        """
        for call in calls:
            args = calls[call]
            for arg in args:
              if len(arg)>0 and arg[-1]=='/': # BUG? Workaround for the above problem.
                logger.debug("Throwing out extraneous '/' character")
                arg = arg[:-1]                #      Throw out the extra '/'
              self.controller._server_dispatch(call, arg)       
            
            
class Browser(gtk.Window):
    """
    GTK Window with Webkit browser used to browse HTML pages and menus on Lib-Ray volume.
    """
    def __init__(self, controller):
        self.ctrl = controller
        gtk.Window.__init__(self)
        self.fullscreen()

        self._browser= webkit.WebView()
        self.add(self._browser)
        self.connect('destroy', gtk.main_quit)
        self._browser.connect("navigation-policy-decision-requested", 
            self._navigation_filter)

        self._browser.open(config.PLAYER_MENU_URL)
        self.show_all()
        
    def _navigation_filter(self, web_view, frame, request, action, policy):
        uri=request.get_uri()
        
        return False

    def load(self, path):
        self._browser.open(path)
        
    def focus(self):
        self.grab_focus()
        self.fullscreen()
        self.show_all()
        self.present()
        
class Cinema(gtk.Window):
    """
    GTK Window with VLC video player used to view movies from the Media directory.
    """
    _vlc = vlc.Instance()
    def __init__(self, controller):
        self.ctrl = controller
        gtk.Window.__init__(self)
        self.player = self._vlc.media_player_new()               
        self.connect("map", self._handle_embed)
        self.connect("destroy", gtk.main_quit)
        #self.connect("key-press-event", self._key_press_handler)
        self.set_size_request(320, 200)
        self.show_all()
        #self.fullscreen()
        self.event_manager = self.player.event_manager()
        self.event_manager.event_attach(vlc.EventType.MediaPlayerEndReached, self.ctrl._autoquit, 1)
        
        
    def _handle_embed(self, *args):
        self.player.set_xwindow(self.window.xid)
        return True
        
    def _key_press_handler(self, widget, event) :
        if event.string == "q" :
            gtk.main_quit()
        
    def load(self, path):
        self.player.set_media(self._vlc.media_new(path))
        
    def focus(self):
        self.grab_focus()
        self.fullscreen()
        self.show_all()
        self.present()
        
    def play(self, bookmark=(None,None)):
        if bookmark[0]:
            self.player.set_position(bookmark[0])
        self.player.play()
        self.player.audio_set_volume(self.player.audio_get_volume()) # Twiddle volume (fix sound glitches?)
        
    def stop(self):
        self.player.stop()
        
    # Playback controls
    def set_volume(self, v):
        self.player.audio_set_volume(v)
    
    def inc_volume(self, inc):
        self.set_volume(min(100, self.player.audio_get_volume() + inc))
                
    def dec_volume(self, dec):
        self.set_volume(max(0, self.player.audio_get_volume() - dec))
 
    def set_chapter(self, c):
        self.player.set_chapter(c)
 
    def inc_chapter(self):
        self.set_chapter(min(self.player.get_chapter_count()-1, self.player.get_chapter() + 1))
        
    def dec_chapter(self):
        self.set_chapter(max(0, self.player.get_chapter() - 1))
 
 
def test():
    """
    Testing elements of the file.
    """
    c = Controller()   
    s = Server()
    v = Cinema(c)
    b = Browser(c)
    #b.load('/project/terry/Lib-Ray/Dev/lib-ray-video-tools-testfixtures/volumes/0.4/index.html')
    b.focus()
    c.connect(b,v)
    s.start(c)
    b.load('http://localhost:%d/player' % s.port)
    v.load('../../lib-ray-video-tools-testfixtures/volumes/0.4/Media/feature.mkv')
    v.focus()
    v.play()
    gtk.main()
    
if __name__=='__main__':
    test()
    